package chair;

/**
 * Relique d'un système de listener/caller Le thalamus écoute à intervalle
 * régulier d'éventuels changements des inputs sensoriels, ordonne à l'organe de
 * provoque réponse réflexe et d'informer modèle conditionnement si tel est le
 * cas
 * 
 * @author nomhad
 * 
 */

final class Thalamus extends Thread {
	/** FIXME: structure extensible ou lever erreur si plein */
	private static final int MAX_LISTENER_CALLERS = 7;
	/**
	 * Les organes reliés au thalamus
	 * 
	 * FIXME: ne devrait avoir accès qu'à hasChanged() et fire(). vice-versa
	 * pour Brain au passage
	 */
	private Organ[] listOrgans;
	/** Liste des organes ayant perçus un nouveau stimulus */
	private boolean[] listChanged;
	/**
	 * date à laquelle l'entrée sensorielle d'un organe a été modifiée pour la
	 * dernière fois. Initialisé à dans get()
	 */
	private long[] listChangedTime;
	/** Quel sont les organes significatifs pour définir contexte ? */
	private boolean[] contextVector;
	/** Nombre d'organes actuellement à l'écoute */
	private int numOrganCaller = 0;
	/** Indiquera où se trouve Hippocampus */
	private Brain _brain;

	Thalamus(Brain brain) {
		super("Thalamus");
		_brain = brain;
		listOrgans = new Organ[MAX_LISTENER_CALLERS];
		listChanged = new boolean[MAX_LISTENER_CALLERS];
		listChangedTime = new long[MAX_LISTENER_CALLERS];
		contextVector = new boolean[MAX_LISTENER_CALLERS];
	}

	/**
	 * Quand le moment est opportun Brain lance le thread
	 */
	void init() {
		synchronized (this) {
			if (!isAlive()) {
				setDaemon(true);
				// setPriority(Thread.MAX_PRIORITY);
				start();
			}
		}
	}

	/**
	 * Ajoute un organe à écouter
	 * 
	 * @param org
	 *            nouvel organe dont il faut s'occuper
	 */
	synchronized void add(Organ org) {
		int i;
		for (i = 0; i < numOrganCaller; i++)
			if (listOrgans[i] == org)
				break;
		// OrganCaller existe pas déjà : on ajoute
		if (i == numOrganCaller) {
			listOrgans[numOrganCaller++] = org;
		}
	}

	/**
	 * Un changement du contexte a été perçu au cours de run(). Que ce soit une
	 * information qui ait changé ou bien qu'elle ne soit plus pertinente pour
	 * connaître l'environnement, il faut envoyer le Context actuel à
	 * Hippocampus.
	 * 
	 * NB: avec valeur -2 des changements ont pu s'effectuer en silence.
	 */
	private void sendContext() {
		// Si Brain n'a pas jugé bon d'avoir un hippocampe on s'en va
		if (_brain.getHippocampus() == null)
			return;

		// Attention, comme il nous faut une valeur en plus pour dire
		// "ne pas en tenir compte" : tableau de bytes au lieu de booléens
		byte[] context = new byte[contextVector.length];
		for (int i = 0; i < context.length; i++) {
			if (contextVector[i] && i < numOrganCaller)
				// Bon, refuse ternaire sans cast, plus à un if près
				context[i] = listOrgans[i].getSensing();
			else
				context[i] = -1;
		}
		_brain.getHippocampus().newContext(new Context(context));
	}

	@Override
	public void run() {
		// Le context a été modifié
		boolean contextChanged = false;

		// Temps pour listChangedTime[]
		long time = Chronos.top();

		// On initialise l'état des organes ainsi que la liste des candidats
		// possibles au contexte. Envoie dès le début réflexes et stimuli.
		for (int i = 0; i < numOrganCaller; i++) {
			if (listOrgans[i].hasChanged())
				listOrgans[i].fire();
			/*
			 * On donne un joker, organ peut-être besoin d'un peu de temps pour
			 * se décider la première fois (typiquement : avant d'explorer au
			 * minimum l'environnement ne peut pas donner une valeur fiable)
			 * 
			 * NB: vient d'ajouter sensation -2 exprès pour OrganCage, pour le
			 * coup pourrait remplacer ce mécanisme (organe renvoie -2 tant que
			 * pas décidé)...mais pourrait juste être expérimentateur pressé qui
			 * va pas appuyer sur un bouton dès lancement et qui ne veut quand
			 * même pas le disqualifier pour un contexte.
			 */
			listChangedTime[i] = -1;
			// On retire de la liste des contextes possibles les organes
			// associés à une récompense/punition
			if (!listOrgans[i].isNeutral())
				contextVector[i] = false;
			else
				// sinon ils sont déjà candidats (afin de pouvoir retrouver
				// immédiatement un contexte déjà connu)
				contextVector[i] = true;
		}

		// Ce contexte ne va peut-être pas faire long feu mais on lui donne sa
		// chance
		sendContext();

		// singleton.setPriority(Thread.MAX_PRIORITY);
		for (;;) {
			time = Chronos.top();

			// boucle sur les organes pour connaître leur mesure
			for (int i = 0; i < numOrganCaller; i++) {
				listChanged[i] = listOrgans[i].hasChanged();
				if (listChanged[i]) {
					// Ordonne d'informer modèle + toggle response
					listOrgans[i].fire();
					// Si c'est une information déterminante pour le contexte
					if (contextVector[i]) {
						contextChanged = true;
						// Si l'état précédent a été trop bref on le sort de la
						// liste des contextes possibles
						// TODO: trouver autre chose quand sortira des booléens
						if (time - listChangedTime[i] < Chronos
								.getContextWindowTime()
								&& listChangedTime[i] != -1) {
							contextVector[i] = false;
						}
						// Sinon il est encore utile de monitorer dernier
						// changement
						else
							listChangedTime[i] = time;
					}
				}
			}

			// il faut informer Hippocampus du changement de contexte
			if (contextChanged) {
				sendContext();
				contextChanged = false;
			}

			synchronized (this) {
				try {
					this.wait(Chronos.getThalamusPollingTime());
				} catch (InterruptedException e) {
				}

			}

		}
	}
}
